﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GBPVRLibrary.MetadataLibrary.TheTVDBInterface;
using GBPVR.Public;
using System.IO;
using System.Text.RegularExpressions;

namespace GBPVRLibrary.MetadataLibrary
{
    public class MetadataFinder
    {
        /// <summary>
        /// Does a best effort find for metadata and returns a single result
        /// </summary>
        /// <param name="SearchText">the search text (could be a short filename)</param>
        /// <param name="Filename">optional filename, will save files to the path if available</param>
        /// <returns>a best effort result</returns>
        public static GBPVRLibrary.MetadataLibrary.VideoMetadata FindMetadata(string SearchText, string Filename)
        {
            if (TheTVDB.RegexSeason.IsMatch(SearchText) && TheTVDB.RegexEpisode.IsMatch(SearchText))
            {
                return null;
                return FindTVMetadata(SearchText, Filename);
            }
            else
            {
                GBPVRLibrary.MetadataLibrary.VideoMetadata metadata = FindMovieMetadata(SearchText, Filename,  "TheMovieDB");
                if (metadata != null)
                    return metadata;                
                metadata = FindMovieMetadata(SearchText, Filename, "IMDB"); // fall back to imdb if themoviedb fails to find anything
                if (metadata != null)
                {
                    GBPVRLibrary.MetadataLibrary.VideoMetadata moviedbmetadata = FindMovieMetadata(metadata.Title, Filename, "TheMovieDB");
                    if (moviedbmetadata != null)
                    {
                        metadata.Dispose();
                        return moviedbmetadata;
                    }
                }
                return metadata;
            }
        }

        /// <summary>
        /// Finds Metadata for a TV Show
        /// </summary>
        /// <param name="SearchText">the Search text for the TV Show</param>
        /// <param name="Filename">the filename of tv show, can be null</param>
        /// <returns>Metadata for the TV Show, or null if not found</returns>
        public static GBPVRLibrary.MetadataLibrary.VideoMetadata FindTVMetadata(string SearchText, string Filename)
        {
            FileInfo File = null;
            if (!String.IsNullOrEmpty(Filename))
                File = new FileInfo(Filename);
            // its a tv show!
            //Logger.Verbose("Looking up tv item: " + SearchText);
            string dir = null;
            if (File != null)
            {
                dir = File.DirectoryName;
                if(Regex.IsMatch(File.DirectoryName, @"\\(Season|Series) [\d]+$", RegexOptions.IgnoreCase))
                    dir = dir.Substring(0, dir.LastIndexOf(@"\"));
                
            }
            //new BatchMetadataLoader().Download(items, BatchMetadataLoader.MetadataType.TVDBEpisode);
            GBPVRLibrary.MetadataLibrary.TheTVDBInterface.TheTVDB tvdb = new GBPVRLibrary.MetadataLibrary.TheTVDBInterface.TheTVDB();
            TheTVDBSeries series = File == null ? null : TheTVDBSeries.LoadSeriesFromPath(dir);
            bool seriesWasNull = series == null;
            GBPVRLibrary.MetadataLibrary.VideoMetadata tvmd = tvdb.FindEpisodeMetadata(SearchText, ref series) as GBPVRLibrary.MetadataLibrary.VideoMetadata;
            
            if (File != null && seriesWasNull && series != null)
                series.SaveToPath(dir);
            return tvmd;
        }

        /// <summary>
        /// Finds Movie Metadata from a specified source and search text
        /// </summary>
        /// <param name="SearchText">the text used to search for the movie</param>
        /// <param name="Filename">the filename of the movie, can be null</param>
        /// <param name="SourceName">the name of lookup source, can be null</param>
        /// <returns>the Metadata for the movie, or null if not found</returns>
        public static GBPVRLibrary.MetadataLibrary.VideoMetadata FindMovieMetadata(string SearchText, string Filename, string SourceName)
        {
            string source = String.IsNullOrEmpty(SourceName) ? "TheMovieDB" : SourceName;
            // assume its a movie
            //Logger.Verbose("Looking up video item: " + SearchText);
            //new BatchMetadataLoader().Download(items, BatchMetadataLoader.MetadataType.Source, "TheMovieDB");
            GBPVRLibrary.MetadataLibrary.MetadataLookup MetadataLookup = new GBPVRLibrary.MetadataLibrary.MetadataLookup();
            DateTime now = DateTime.Now;
            List<GBPVRLibrary.MetadataLibrary.MetadataLookupSearchResult> results = MetadataLookup.SearchMediaTypeSource("Video", source, SearchText);
            TimeSpan timetaken = DateTime.Now.Subtract(now);
            if (results == null || results.Count == 0)
                return null;
            string url = null;
            int minimum = int.MaxValue;
            foreach (GBPVRLibrary.MetadataLibrary.MetadataLookupSearchResult result in results)
            {
                if (result.Title.Equals(SearchText))
                {
                    url = result.Url;
                    break;
                }
                int distance = CompareStrings(SearchText, result.Title);
                if (distance < minimum && distance < 5)
                {
                    url = result.Url;
                    minimum = distance;
                }
            }

            if (String.IsNullOrEmpty(url) && SearchText.LastIndexOf(" - ") > 0)
            {
                string search2 = SearchText.Substring(SearchText.LastIndexOf(" - ") + 3).Trim();
                if (String.IsNullOrEmpty(search2))
                    return null;
                results = MetadataLookup.SearchMediaTypeSource("Video", source, search2);
                foreach (GBPVRLibrary.MetadataLibrary.MetadataLookupSearchResult result in results)
                {
                    int distance = CompareStrings(search2, result.Title);
                    if (distance < minimum && distance < 5)
                    {
                        url = result.Url;
                        minimum = distance;
                    }
                }

                if (String.IsNullOrEmpty(url))
                {
                    string search3 = SearchText.Substring(0, SearchText.IndexOf(" - ")).Trim();
                    if (String.IsNullOrEmpty(search3))
                        return null;
                    results = MetadataLookup.SearchMediaTypeSource("Video", source, search3);
                    foreach (GBPVRLibrary.MetadataLibrary.MetadataLookupSearchResult result in results)
                    {
                        int distance = CompareStrings(search3, result.Title);
                        if (distance < minimum && distance < 5)
                        {
                            url = result.Url;
                            minimum = distance;
                        }
                    }
                }
            }
            if (String.IsNullOrEmpty(url))
                return null;
            //if (String.IsNullOrEmpty(url))
                //url = results[0].Url;
            timetaken = DateTime.Now.Subtract(now);
            return MetadataLookup.LoadItem("Video", source, url, typeof(GBPVRLibrary.MetadataLibrary.VideoMetadata)) as GBPVRLibrary.MetadataLibrary.VideoMetadata;
        }

        /// <summary>
        /// Compares 2 strings to see how similar they are, the smaller the number the more similar they are
        /// </summary>
        /// <param name="String1">the first string</param>
        /// <param name="String2">the second string</param>
        /// <returns>the distance between the two strings</returns>
        public static int CompareStrings(string String1, string String2)
        {
            if (String.IsNullOrEmpty(String1) || String.IsNullOrEmpty(String2))
                return int.MaxValue;
            Regex replacer = new Regex(@"((?<=(^|[\s]))(the|a)(?=([\s]|$)))|[',\.!@#\$%\^\&\*\(\)\\-_+=\[\]\|:;""'<>/\?]|[\s]*", RegexOptions.IgnoreCase);
            return LevenshteinDistance(replacer.Replace(String1, ""), replacer.Replace(String2, ""));
        }

        /// <summary>
        /// Strips down the search text to be more flexible (removes numbers, puncation etc)
        /// </summary>
        /// <param name="SearchText">the search text to strip</param>
        /// <returns>a more flexible search string</returns>
        public static string MetadataSearchText(string SearchText)
        {
            string result = Regex.Replace(Regex.Replace(SearchText, @"(?<=([\s]|^))[^a-zA-Z]*(?=([\s]|$))|([~`!@#\$%\^&\*\(\)_\-+=\{\}\[\]\|\\:;""'<,>.\?/])", ""), "[ ]{2,}", " ").Trim();
            if (String.IsNullOrEmpty(result)) // need to check this, eg if the movie is "300" our regex would return ""
                return SearchText;
            return result;
        }

        /// <summary>
        /// Compute Levenshtein distance
        /// </summary>
        /// <param name="s">String 1</param>
        /// <param name="t">String 2</param>
        /// <returns>Distance between the two strings.
        /// The larger the number, the bigger the difference.
        /// </returns>
        public static int LevenshteinDistance(string s, string t)
        {
            int n = s.Length;
            int m = t.Length;
            int[,] d = new int[n + 1, m + 1];
            // Step 1
            if (n == 0)
                return m;
            if (m == 0)
                return n;
            // Step 2
            for (int i = 0; i <= n; d[i, 0] = i++) { }
            for (int j = 0; j <= m; d[0, j] = j++) { }
            // Step 3
            for (int i = 1; i <= n; i++)
            {
                //Step 4
                for (int j = 1; j <= m; j++)
                {
                    // Step 5
                    int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
                    // Step 6
                    d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
                }
            }
            // Step 7
            return d[n, m];
        }
    }
}
